﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Platform.Linq;
using Platform.ObjectModel;
using Platform.Storage.Entity;
using System.Diagnostics;

namespace Platform.Storage.Impl
{

	/// <summary>
	/// Realiza o mapeamentos entre as propriedades da entidade que está sendo acessada e a tabela
	/// do tipo chave-valor
	/// </summary>
	public sealed class EntityQueryTranslator : ExpressionVisitor //, IQueryTranslator
	{
		private ContextManager contexts;

		/// <summary>
		/// 
		/// </summary>
		internal EntityQueryTranslator()
		{ }

		/// <summary>
		/// 
		/// </summary>
		[Obsolete("Os dados que são fornecidos pela interface IQueryResolver, estão no contexto da pilha.")]
		internal IQueryResolver Expander
		{ get; set; }

		/// <summary>
		/// Obtém o contexto de tradução atual
		/// </summary>
		internal Context Context
		{ get { return this.contexts.Current; } }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		protected override Expression VisitConstant( ConstantExpression node )
		{
			if ( node.Type.IsQueryable()
				&& node.Type.IsGenericType 
				&& node.Type.Implements<IQueryResolver>() )
			{
				// Nesse ponto, é identificado a entidade pela qual está sendo pesquisado
				// precisando fazer a pesquisa por todos os campos dessa entidade, selecionando
				// somente os campos que começam com o "namespace + name" do schema da entidade

				// Tipo da entidade
				Type entityType = node.Type.GetGenericArguments()[ 0 ];
				IQueryResolver resolver = ( IQueryResolver )node.CompileAndGetValue();
				var name = resolver.GetSchema( entityType ).QualifiedName;
				//var name = this.Expander.GetDataObjectName( entityType );

				if ( name != null )
				{
					// Obtém o nome do schema que representa a entidade
					string fullName = name.FullName.ToLowerInvariant();
					var fields = resolver.Fields;
					//var fields = this.Expander.Fields;

					var queryBase = fields
						.StartingWith( fullName )
						.StripLambda( 1 );

					var scope = this.contexts.CreateScope( node, queryBase, name, entityType, resolver );
					//scope.QualifiedName = name;
					//scope.EntityType = entityType;

					return queryBase;
				}
			}
			return base.VisitConstant( node );
		}

		protected override Expression VisitBinary( BinaryExpression node )
		{
			switch ( node.NodeType )
			{
				case ExpressionType.Or:
				case ExpressionType.OrElse:
				case ExpressionType.And:
				case ExpressionType.AndAlso:
					var parent = this.Context;
					using ( Context scope = this.contexts.CreateScope( parent ) )
					{
						var result = base.VisitBinary( node );
						scope.Criterias.ForEach(
							c =>
							{
								c.KindOf = ( node.NodeType == ExpressionType.OrElse || node.NodeType == ExpressionType.Or ) ? CriteriaType.Inclusion : CriteriaType.Exclusion;
								parent.Criterias.Add( c );
							} );
						return result;
					}

				case ExpressionType.Equal:
				case ExpressionType.NotEqual:
				case ExpressionType.GreaterThan:
				case ExpressionType.GreaterThanOrEqual:
				case ExpressionType.LessThan:
				case ExpressionType.LessThanOrEqual:
					this.Context.AddCriteria( node );
					return node;
			}

			return base.VisitBinary( node );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		protected override Expression VisitMethodCall( MethodCallExpression node )
		{
			string methodName = node.Method.Name;

			if ( node.Method.DeclaringType.IsQueryable() )
			{
				this.contexts.PushMethod( node.Method.Name );

				var query = base.Visit( node.Arguments[ 0 ] );
				Type entityType = node.Method.GetGenericArguments()[ 0 ];

				// possui critérios para filtrar
				if ( node.HasCriteria() )	// Where, FirstOrDefault(...), ...
				{
					var context = this.Context;

					base.Visit( node.Arguments[ 1 ] );
					// Depois será movido para o método "VisitBinary" 
					//var criteria = node.Arguments[ 1 ].Find<BinaryExpression>();
					//context.AddCriteria( criteria );
				}
				
				if ( methodName.StartsWith( "First" ) )
				{
					this.Context.TakeCount = Expression.Constant( 1 );
				}
				else if ( methodName == "Take" )
				{
					 this.Context.TakeCount = node.Arguments[ 1 ];
				}
				else if ( methodName == "Skip" )
				{
					this.Context.SkipCount = node.Arguments[ 1 ];
				}
				else if ( methodName == "Count" )
				{
					this.Context.SetAggregate( methodName );
				}

				// Se é um contexto diferente, então um contexto foi criado para essa chamada
				if ( this.Context.ScopeFinished( node.Method.Name ) )
				{
					return this.contexts.QueryFromContext();
				}
				else
				{
					return node;
				}
			}

			throw new NotSupportedException( String.Format( "Método não suportado \"{0}\".", node.Method.Name ) );
		}

		protected override Expression VisitLambda<T>( Expression<T> node )
		{
			return base.VisitLambda<T>( node );
		}

		#region IQueryTranslator Members

		/// <summary>
		/// Realiza a tradução
		/// </summary>
		/// <param name="source"></param>
		/// <returns></returns>
		public Expression Translate( Expression source )
		{
			//this.contexts = new ContextManager( this.Expander );
			this.contexts = new ContextManager();
			return base.Visit( source );
		}

		#endregion
	}
}
